package cn.com.godliu.oauth2.config;

import cn.com.godliu.oauth2.config.jwt.Jwt_TokenEnhancer;
import cn.com.godliu.oauth2.service.AccountUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    ClientDetailsService detailsService;

    @Autowired
    AccountUserDetailsService accountUserDetailsService;

    //=============JWT存储token==================
    @Resource
    private TokenStore tokenStore;
    @Resource
    private JwtAccessTokenConverter accessTokenConverter;
    @Resource
    private Jwt_TokenEnhancer jwtTokenEnhancer;
    //=============JWT存储token==================

    @Autowired
    private DataSource dataSource;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    /**
     * 配置令牌访问端点得安全约束
     * 必须设置allowFormAuthenticationForClients 否则没有办法用postman获取token
     * 也需要指定密码加密方式BCryptPasswordEncoder
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //系统默认会拒绝一切token请求，所以要重载配置
        security.tokenKeyAccess("permitAll()") //放开所有获取token请求
                .checkTokenAccess("permitAll()") //检查token的请求也要放开
                .allowFormAuthenticationForClients();//支持客户端发来的校验请求
    }

    /**
     * //配置客户端详情，客户端详情可以通过数据库查询，（校验哪些客户端可以申请令牌）
     * clientid 客户端id
     * secret 客户端安全码
     * scope 用来限制客户端得访问范围，默认为空，如果为空，那么客户端拥有全部得访问范围
     * authorizedGrantTypes  此客户端可以使用得授权类型，默认为空，oauth2提供了五种授权类型
     * authorities 此客户端可以使用 得权限  基于spring security authorities（一般不使用）
     *
     * @param clients
     * @throws Exception 这里客户端信息存在数据库
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //给第三方客户端的配置，记录哪些客户端可以从我这拿认证
        /***********************内存模式***********************/
        //clients.inMemory().withClient("godliu") //设置客户端id
        //        .redirectUris("https://www.baidu.com") //设置回调地主，这个地址后边会跟附加授权码
        //        .autoApprove(false) //关闭自动通过，需要用户自己手动点击确认
        //        .scopes("all") //作用域 针对资源的操作权限 读  写
        //        .secret(bCryptPasswordEncoder.encode("helloworld")) //客户端认证密码
        //        .authorizedGrantTypes(
        //                "authorization_code", //授权码模式
        //                "password",//密码模式
        //                "client_credentials",//客户端模式
        //                "implicit",//简化模式
        //                "refresh_token"//刷新token
        //        ) //token生成策略配置类型
        //        .resourceIds("r1","r2");//要访问的资源编号
        /*************************数据库模式**************************************/
        clients.withClientDetails(clientDetailsService());
    }

    public ClientDetailsService clientDetailsService() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(bCryptPasswordEncoder);
        return jdbcClientDetailsService;
    }

    /**
     * 配置令牌访问端点url，和令牌服务（令牌生成策略，如何生成）
     * /oauth/authorize   授权端点
     * 获取授权码
     * http://localhost:8762/oauth/authorize?response_type=code&client_id=resources-service&scope=all&client_secret=123456&redirect_uri=http://www.baidu.com
     * /oauth/token       获取令牌端点（发送post请求，获取token，code=申请的授权码）
     * <p>
     * /oauth/confirm_access  用户确认授权端点
     * /oauth/error  授权服务错误信息端点
     * /oauth/check_token  用于资源服务访问的令牌解析端点
     * /oauth/token_key  提供公有密钥的端点，如果使用jwt令牌的话
     * 授权端点url应该被spring-security 保护起来，只供授权用户访问
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager) //密码模式
                .authorizationCodeServices(authorizationCodeServices)//授权码模式
                .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET) //设置允许POST和GET方法
                .tokenServices(getTokenService()) //需要配置tokenService
                .accessTokenConverter(accessTokenConverter)
                .userDetailsService(accountUserDetailsService);
    }

    /**
     * 设置token生成策略
     *
     * @return
     */
    public AuthorizationServerTokenServices getTokenService() {

        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore);//TOKEN存储方式
        defaultTokenServices.setSupportRefreshToken(true);//支持刷新token

        //令牌增强，使用jwt令牌
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        //在这里设置jwtAccessTokenConverter，但是发现不起作用，令牌还是普通令牌，加到发现可以
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, accessTokenConverter));
        defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);

        defaultTokenServices.setClientDetailsService(detailsService);
        defaultTokenServices.setAccessTokenValiditySeconds(7200);//令牌默认有效两小时-->适用于内存模式
        defaultTokenServices.setRefreshTokenValiditySeconds(259200);//刷新令牌默认有效期三天-->适用于内存模式
        return defaultTokenServices;
    }

    //设置授权码模式的授权码如何存取
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        return new JdbcAuthorizationCodeServices(dataSource);
    }
}
